package org.goobs.internet;

import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class WebForm {
	private static final Pattern formPat = Pattern.compile("< *?[Ff][Oo][Rr][Mm].*?>", Pattern.MULTILINE | Pattern.DOTALL);
	private static final Pattern inputPat = Pattern.compile("< *?[Ii][Nn][Pp][Uu][Tt].*?/>", Pattern.MULTILINE | Pattern.DOTALL);
	
	public static final int NONE = 0;
	public static final int GET = 1;
	public static final int POST = 2;
	
	private String name;
	private String action;
	private int method;
	private HashMap <String, String> props = new HashMap <String, String> ();
	private HashMap <String, Input> inputs = new HashMap <String, Input> ();
	
	private class Input{
		private String name, type, value;
		private HashMap <String, String> props = new HashMap <String, String> ();
		private Input(String name, String type, String value){
			this.name = name;
			this.type = type;
			this.value = value;
		}
		private Input(String name, String type){
			this(name, type, null);
		}
		private Input(String name){
			this(name, null, null);
		}
		private Input(){}
		public String getName(){
			return name;
		}
		public String getValue(){
			return value;
		}
		public String getType(){
			return type;
		}
		public String getProperty(String key){
			return props.get(key.toLowerCase());
		}
		public void setValue(String val){
			value = val;
		}
		public String toString(){
			StringBuilder b = new StringBuilder();
			b.append(name).append("\t\t");
			if(value == null){
				b.append("[none]");
			}else{
				b.append(value);
			}
			return b.toString();
		}
	}
	
	public WebForm(String name, String action, HashMap <String, String> values, int method){
		this.name = name;
		this.action = action;
		this.method = method;
		for(String key : values.keySet()){
			String val = values.get(key);
			Input in = new Input(key, "text", val);
			inputs.put(key, in);
		}
	}
	
	protected WebForm(String rawHtml){
		//--Get top level form tags
		//(get form field)
		Matcher formMatch = formPat.matcher(rawHtml);
		if(!formMatch.find()){
			throw new IllegalArgumentException("no FORM field in html: " + rawHtml);
		}
		String mainTags = formMatch.group().replaceAll(" ", "  ");
		//(get tags)
		Collection <String> valMatch = getValues(mainTags);
		for(String raw : valMatch){
			if(raw.charAt(raw.length()-1) == '>'){
				raw = raw.substring(0, raw.length()-1);
			}
			int eq = raw.indexOf('=');
			String key = raw.substring(0, eq).trim().toLowerCase();
			String val = raw.substring(eq+1, raw.length()).trim();
			if(val.charAt(0) == '\"' && val.charAt(val.length()-1) == '\"'){
				val= val.substring(1, val.length()-1);
			}
			if(key.equalsIgnoreCase("name")){
				this.name = val;
			}else if(key.equalsIgnoreCase("id") && name == null){
				this.name = val;
			}else if(key.equalsIgnoreCase("action")){
				this.action = val;
			}else if(key.equalsIgnoreCase("method")){
				if(val.equalsIgnoreCase("get")){
					method = GET;
				}else if(val.equalsIgnoreCase("post")){
					method = POST;
				}else{
					throw new IllegalArgumentException("Invalid method field in form: " + val);
				}
			}else{
				props.put(key, val);
			}
		}
		
		//--Get inputs
		Matcher inputMatch = inputPat.matcher(rawHtml);
		while(inputMatch.find()){
			Input toAdd = new Input();
			String inputStr = inputMatch.group().replaceAll(" ", "  ");
			valMatch = getValues(inputStr);
			for(String raw : valMatch){
				//(remove trailing spaces or end brackets)
				if(raw.charAt(raw.length()-1) == '>'){
					raw = raw.substring(0, raw.length()-1).trim();
					if(raw.charAt(raw.length()-1) == '/'){
						raw = raw.substring(0, raw.length()-1).trim();
					}
				}
				int eq = raw.indexOf('=');
				String key = raw.substring(0, eq).trim().toLowerCase();
				String val = raw.substring(eq+1, raw.length()).trim();
				if(val.length() > 1 && val.charAt(0) == '\"' && val.charAt(val.length()-1) == '\"'){
					val= val.substring(1, val.length()-1);
				}
				if(key.equals("name")){
					toAdd.name = val;
				}else if(key.equals("type")){
					toAdd.type = val;
				}else if(key.equals("value")){
					toAdd.value = val;
				}else{
					toAdd.props.put(key, val);
				}
			}
			if(toAdd.name == null || toAdd.name.equals("")){
//				System.err.println("[WARNING]: form input has no name; dropping: " + inputStr);
			}else{
				inputs.put(toAdd.name, toAdd);
			}
		}
	}
	
	public String getName(){
		return name;
	}
	
	public String getAction(){
		return action;
	}
	
	public String getProperty(String key){
		return props.get(key.toLowerCase());
	}
	
	public int getMethod(){
		return method;
	}
	
	public boolean setValue(String name, String value){
		Input input = inputs.get(name);
		if(input == null){
			return false;
		}
		input.setValue(value);
		return true;
	}
	
	public String getValue(String name){
		Input input = inputs.get(name);
		if(input == null){
			return "";
		}else{
			return input.getValue();
		}
	}
	
	public Collection <String> getInputs(){
		return inputs.keySet();
	}
	
	public String toString(){
		StringBuilder b = new StringBuilder();
		b.append(name).append("\n");
		b.append("\taction: ");
		b.append(action).append("\n");
		
		for(String input : inputs.keySet()){
			b.append("\t").append(inputs.get(input)).append("\n");
		}
		
		return b.substring(0, b.length()-1);
	}
	
	
	private static Collection <String> getValues(String input){
		//--Weed out the beginning tag
		char[] inputChar = input.toCharArray();
		int cutPos = 0;
		while(inputChar[cutPos] == '<' || inputChar[cutPos] == ' '){
			cutPos++;
			if(cutPos >= inputChar.length){
				throw new IllegalArgumentException("Could not assess type of tag: " + input);
			}
		}
		while(inputChar[cutPos] != ' '){
			cutPos++;
			if(cutPos >= inputChar.length){
				throw new IllegalArgumentException("Could not assess type of tag: " + input);
			}
		}
		//--Set up the data structures
		Collection <String> rtn = new HashSet <String> ();
		char[] in = input.trim().substring(cutPos).toCharArray();
		int state = 0; //0: in whitespace, 1: in text, 2: in quotes
		boolean haveSeenEquals = false;
		StringBuilder current = new StringBuilder();
		//--Get the values
		for(int i=0; i<in.length; i++){
			char c = in[i];
			//(quotes)
			if(c == '\"'){
				if(state == 2){
					//(end a segment)
					state = 0;
					rtn.add(current.toString());
					current = new StringBuilder();
					if(!haveSeenEquals){
						throw new IllegalArgumentException("invalid input string (no equals for value " + current + "): " + input);
					}
					haveSeenEquals = false;
				}else{
					state = 2;
				}
			}
			//(whitespace)
			else if(c == ' '){
				if(state == 1){
					//(end a segment)
					state = 0;
					rtn.add(current.toString());
					current = new StringBuilder();
					if(!haveSeenEquals){
						throw new IllegalArgumentException("invalid input string (no equals for value " + current + "): " + input);
					}
					haveSeenEquals = false;
				}else if(state == 2){
					current.append(c);
				}
			}
			//(equals)
			else if(c == '='){
				if(state != 2 && haveSeenEquals){
					throw new IllegalArgumentException("More than one equals in value tag (so far " + current + "): " + input);
				}
				haveSeenEquals = true;
				current.append(c);
			}
			//(text)
			else{
				current.append(c);
			}
		}
		//--Return
		if(!current.toString().contains(">")){
			throw new IllegalArgumentException("input did not end with close tag '/>': " + input);
		}
		return rtn;
	}
}
